package org.fujay.langchain4jserver.infrastructure.persistence.repository;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.fujay.commons.core.util.JsonUtils;
import org.fujay.langchain4jserver.domain.application.vo.AppId;
import org.fujay.langchain4jserver.domain.conversation.aggregate.ConversationAggregate;
import org.fujay.langchain4jserver.domain.conversation.entity.Conversation;
import org.fujay.langchain4jserver.domain.conversation.entity.MessageEntity;
import org.fujay.langchain4jserver.domain.conversation.repository.ConversationRepository;
import org.fujay.langchain4jserver.domain.conversation.repository.MessageRepository;
import org.fujay.langchain4jserver.domain.conversation.vo.*;
import org.fujay.langchain4jserver.infrastructure.persistence.assembler.ConversationAssembler;
import org.fujay.langchain4jserver.infrastructure.persistence.mybatis.entity.ConversationPo;
import org.fujay.langchain4jserver.infrastructure.persistence.mybatis.entity.MessagePo;
import org.fujay.langchain4jserver.infrastructure.persistence.mybatis.mapper.ConversationMapper;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * 会话仓储实现
 */
@Slf4j
@Repository
@RequiredArgsConstructor
public class ConversationRepositoryImpl implements ConversationRepository {

    private final ConversationMapper conversationMapper;
    private final MessageRepository messageRepository;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void save(ConversationAggregate aggregate) {
        Conversation conversation = aggregate.getConversation();
        List<MessageEntity> messages = aggregate.getMessages();

        ConversationPo conversationPO = ConversationAssembler.toPO(conversation);
        if (conversationPO.getId() != null
                && conversationMapper.selectById(conversationPO.getId()) != null) {
            conversationMapper.updateById(conversationPO);
        } else {
            conversationMapper.insert(conversationPO);
        }

        log.warn(
                "Saving messages in ConversationRepositoryImpl needs careful implementation (new/update/delete).");
        // Example saving only new messages:
        List<MessageEntity> newMessages = messages.stream().filter(MessageEntity::isNew).toList();
        if (!newMessages.isEmpty()) {
            messageRepository.saveAll(newMessages, aggregate.getId());
            // TODO: Update isNew flag in memory after saving? Or rely on reload?
        }
        // TODO: Handle deleted messages
    }

    @Override
    public Optional<ConversationAggregate> findById(ConversationId id) {
        ConversationPo conversationPO = conversationMapper.selectById(id.value());
        if (conversationPO == null) {
            return Optional.empty();
        }
        List<MessageEntity> messages = messageRepository.findByConversationId(id);

        Conversation conversation = ConversationAssembler.toDomainEntity(conversationPO);
        // Ensure messageRepository is passed to load
        ConversationAggregate aggregate =
                ConversationAggregate.load(conversation, messages);
        return Optional.of(aggregate);
    }

    @Override
    public List<ConversationAggregate> findByUserAndApp(UserId userId, AppId appId) {
        LambdaQueryWrapper<ConversationPo> queryWrapper =
                Wrappers.<ConversationPo>lambdaQuery()
                        .eq(ConversationPo::getUserId, userId.value())
                        .orderByDesc(ConversationPo::getUpdateTime);
        if (appId != null) {
            queryWrapper.eq(ConversationPo::getAppId, appId.value());
        }

        List<ConversationPo> conversationPOs = conversationMapper.selectList(queryWrapper);
        return conversationPOs.stream()
                .map(ConversationAssembler::toDomainEntity)
                .map(conv -> ConversationAggregate.load(conv, Collections.emptyList()))
                .collect(Collectors.toList());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteById(ConversationId id) {
        // Need to delete messages first or rely on cascade delete
        messageRepository.deleteByConversationId(id);
        conversationMapper.deleteById(id.value());
    }

    /**
     * Checks if there are any active conversations associated with the given application ID. The
     * definition of "active" depends on the business logic (e.g., not marked as closed/archived).
     *
     * @param appId The application ID to check.
     * @return true if active conversations exist for the app, false otherwise.
     */
    @Override
    public boolean hasActiveConversations(AppId appId) {
        /*todo  逻辑未实现*/
        return false;
    }

    // --- Conversion Methods --- (可移至 Assembler)

    private MessageEntity convertMessageToDomain(MessagePo po) {
        if (po == null) return null;
        MessageId id = new MessageId(po.getId());
        MessageType type = MessageType.valueOf(po.getMessageType().toUpperCase());

        TokenUsage usage = null;
        if (StringUtils.isNotBlank(po.getTokenUsageJson())) {
            usage = JsonUtils.parseObject(po.getTokenUsageJson(), TokenUsage.class);
        }

        Map<String, Object> metadata = null;
        if (StringUtils.isNotBlank(po.getMetadataJson())) {
            metadata =
                    JsonUtils.parseObject(
                            po.getMetadataJson(), new TypeReference<>() {
                            });
        }

        // Deserialize image URLs
        List<String> imageUrls = null;
        if (StringUtils.isNotBlank(po.getImageUrlsJson())) { // Assuming PO has imageUrlsJson field
            imageUrls =
                    JsonUtils.parseObject(po.getImageUrlsJson(), new TypeReference<>() {
                    });
        }

        // Call Message.load with imageUrls
        return MessageEntity.load(
                id, type, po.getContent(), po.getTimestamp(), usage, metadata, imageUrls);
    }

    private MessagePo convertMessageToPO(MessageEntity message, ConversationId conversationId) {
        if (message == null) return null;
        MessagePo po = new MessagePo();
        po.setId(message.getId().value());
        po.setConversationId(conversationId.value());
        po.setMessageType(message.getType().name()); // 存储枚举名
        po.setContent(message.getContent());
        po.setTimestamp(message.getTimestamp());

        if (message.getTokenUsage() != null) {
            po.setTokenUsageJson(JsonUtils.toJsonString(message.getTokenUsage()));
        }

        if (message.getMetadata() != null && !message.getMetadata().isEmpty()) {
            po.setMetadataJson(JsonUtils.toJsonString(message.getMetadata()));
        }

        // Add serialization for image URLs
        if (message.getImageUrls() != null && !message.getImageUrls().isEmpty()) {
            po.setImageUrlsJson(
                    JsonUtils.toJsonString(message.getImageUrls())); // Assuming PO has
            // imageUrlsJson field
        }
        // BaseEntity fields auto-handled
        return po;
    }
}
